package com.zsd.android.complier;

import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.WildcardTypeName;
import com.zsd.android.annotation.ARouter;
import com.zsd.android.annotation.model.RouterBean;
import com.zsd.android.complier.utils.Constants;
import com.zsd.android.complier.utils.TextUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;

@AutoService(Processor.class)
@SupportedAnnotationTypes({Constants.AROUTER_ANNOTATION_TYPES})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedOptions({Constants.MODULE_NAME, Constants.APT_PACKAGE})
public class ARouterProcessor extends AbstractProcessor {

    //包含很多操作Elements的工具类
    private Elements elementsUtil;
    //包含用于操作TypeMirror工具类
    private Types typesUtil;
    //用来打印log的
    private Messager messager;
    //用来创建新的源文件，class文件（造币技术）
    private Filer filer;
    //子模块名，如/app/order/personal。需要拼接类名时用到（必传）ARouter$$Group$$order
    private String moduleName;
    //包名，用于存放APT生成的类文件
    private String packageNameForAPT;
    // 临时map存储，用来存放路由组Group对应的详细Path类对象，生成路由路径类文件时遍历
    // key:组名"app", value:"app"组的路由路径"ARouter$$Path$$app.class"
    private Map<String, List<RouterBean>> tempPathMap = new HashMap<>();
    // 临时map存储，用来存放路由Group信息，生成路由组类文件时遍历
    // key:组名"app", value:类名"ARouter$$Path$$app.class"
    private Map<String, String> tempGroupMap = new HashMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        this.elementsUtil = processingEnvironment.getElementUtils();
        this.typesUtil = processingEnvironment.getTypeUtils();
        this.messager = processingEnvironment.getMessager();
        this.filer = processingEnvironment.getFiler();

        Map<String, String> options = processingEnvironment.getOptions();
        if (!TextUtils.isEmpty(options)) {
            moduleName = options.get(Constants.MODULE_NAME);
            packageNameForAPT = options.get(Constants.APT_PACKAGE);
        }
        if (TextUtils.isEmpty(moduleName) || TextUtils.isEmpty(packageNameForAPT)) {
            throw new RuntimeException("注解处理器需要的参数moduleName或则packageNameForAPT为空，请在对应build.gradle配置参数");
        }
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (!TextUtils.isEmpty(set)) {
            //获取所有被@ARouter注解的元素集合
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
            if (!TextUtils.isEmpty(elements)) {
                try {
                    parseElements(elements);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return true;
        }
        return false;
    }

    private void parseElements(Set<? extends Element> elements) throws IOException {
        //通过Elements工具类，获取Activity类型
        TypeElement activityType = elementsUtil.getTypeElement(Constants.ACTIVITY);
        TypeElement callType = elementsUtil.getTypeElement(Constants.CALL);

        //显示类信息
        TypeMirror activityMirror = activityType.asType();
        TypeMirror callMirror = callType.asType();

        for (Element element : elements) {
            //获取每个元素的类信息
            TypeMirror typeMirror = element.asType();

            ARouter aRouter = element.getAnnotation(ARouter.class);
            //路由详细信息封装到实体类
            RouterBean bean = new RouterBean.Builder()
                    .setGroup(aRouter.group())
                    .setPath(aRouter.path())
                    .setElement(element)
                    .build();
            //高级判断，@ARouter注解仅仅只能用在类之上，并且是规定的Activity
            if (typesUtil.isSubtype(typeMirror, activityMirror)) {
                bean.setType(RouterBean.Type.ACTIVITY);
            } else if (typesUtil.isSubtype(typeMirror, callMirror)) {
                bean.setType(RouterBean.Type.CALL);
            } else {
                throw new RuntimeException("@ARouter注解目前仅限用于Activity之上");
            }

            //赋值临时map存储以上信息，用来遍历时生成代码
            valueOfPathMap(bean);
        }
        //ARouterLoadGroup和ARouterLoadPath类型，用来生成类文件时实现接口
        TypeElement groupLoadType = elementsUtil.getTypeElement(Constants.AROUTER_GROUP);
        TypeElement pathLoadType = elementsUtil.getTypeElement(Constants.AROUTER_PATH);
        // 1、生成路由的详细Path类文件，如：ARouter$$path$$app
        createPathFile(pathLoadType);
        // 2、生成路由组Group类文件（没有path类文件，取不到），如：ARouter$$Group$$app
        createGroupFile(groupLoadType, pathLoadType);
    }

    /**
     * 生成路由组Group对应详细Path，如：ARouter$$Path$$app
     *
     * @param pathLoadType ARouterLoadPath接口信息
     */
    private void createPathFile(TypeElement pathLoadType) throws IOException {
        if (TextUtils.isEmpty(tempPathMap)) {
            return;
        }
        //方法的返回值Map<String, RouterBean>
        TypeName methodReturns = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ClassName.get(RouterBean.class));

        //遍历分组，每一个分组创建一个路径类文件，如：ARouter$$Path$$app
        for (Map.Entry<String, List<RouterBean>> entry : tempPathMap.entrySet()) {
            //方法体 public Map<String, RouterBean> loadPath() {}
            MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Constants.PATH_METHOD_NAME) //方法名
                    .addAnnotation(Override.class) //重写注解
                    .addModifiers(Modifier.PUBLIC) //public修饰符
                    .returns(methodReturns); //返回值

            //方法内容
            // 不循环的部分 Map<String, RouterBean> pathMap = new HashMap<>();
            methodBuilder.addStatement("$T<$T, $T> $N = new $T<>()",
                    ClassName.get(Map.class),
                    ClassName.get(String.class),
                    ClassName.get(RouterBean.class),
                    Constants.PATH_PARAMETER_NAME,
                    ClassName.get(HashMap.class));
            //循环部分 app/MainActivity, app/...
            List<RouterBean> pathList = entry.getValue();
            for (RouterBean bean : pathList) {
              /**
                 * pathMap.put("/app/MainActivity", RouterBean.create(RouterBean.Type.ACTIVITY, MainActivity.class, "/app/MainActivity", "app"));
                 */
                methodBuilder.addStatement("$N.put($S, $T.create($T.$L, $T.class, $S, $S))",
                        Constants.PATH_PARAMETER_NAME, //pathMap.put
                        bean.getPath(), // "/app/MainActivity"
                        ClassName.get(RouterBean.class), // RouterBean.create
                        ClassName.get(RouterBean.Type.class), // Router.Type
                        bean.getType(), // 枚举ACTIVITY
                        ClassName.get((TypeElement) bean.getElement()), //MainActivity.class
                        bean.getPath(), // "/app/MainActivity"
                        bean.getGroup()); // "app"
            }
            //不循环部分 return pathMap;
            methodBuilder.addStatement("return $N", Constants.PATH_PARAMETER_NAME);
            //生成类文件，如：ARouter$$Path$app
            String finalClassName = Constants.PATH_FILE_NAME + entry.getKey();
            JavaFile.builder(packageNameForAPT, //包路径
                    TypeSpec.classBuilder(finalClassName)//类名
                            .addSuperinterface(ClassName.get(pathLoadType)) //实现接口
                            .addModifiers(Modifier.PUBLIC)
                            .addMethod(methodBuilder.build()) //方法的构建
                            .build())
                    .build().writeTo(filer);

            // 别忘了，非常重要赋值
            tempGroupMap.put(entry.getKey(), finalClassName);
        }
    }

    /**
     * 生成路由组Group文件，如：ARouter$$Group$$app
     *
     * @param groupLoadType ARouterLoadGroup接口信息
     * @param pathLoadType ARouterLoadPath接口信息
     */
    private void createGroupFile(TypeElement groupLoadType, TypeElement pathLoadType) throws IOException {
        if (TextUtils.isEmpty(tempGroupMap)) {
            return;
        }
        TypeName methodReturns = ParameterizedTypeName.get(
                ClassName.get(Map.class), // Map
                ClassName.get(String.class), // Map<String,
                // 第二个参数：Class<? extends ARouterLoadPath>
                // 某某Class是否属于ARouterLoadPath接口的实现类
                ParameterizedTypeName.get(ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(pathLoadType)))
        );

        // 方法配置：public Map<String, Class<? extends ARouterLoadPath>> loadGroup() {
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Constants.GROUP_METHOD_NAME) // 方法名
                .addAnnotation(Override.class) // 重写注解
                .addModifiers(Modifier.PUBLIC) // public修饰符
                .returns(methodReturns); // 方法返回值

        // 遍历之前：Map<String, Class<? extends ARouterLoadPath>> groupMap = new HashMap<>();
        methodBuilder.addStatement("$T<$T, $T> $N = new $T<>()",
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ParameterizedTypeName.get(ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(pathLoadType))),
                Constants.GROUP_PARAMETER_NAME,
                HashMap.class);

        // 方法内容配置
        for (Map.Entry<String, String> entry : tempGroupMap.entrySet()) {
            // 类似String.format("hello %s net163 %d", "net", 163)通配符
            // groupMap.put("main", ARouter$$Path$$app.class);
            methodBuilder.addStatement("$N.put($S, $T.class)",
                    Constants.GROUP_PARAMETER_NAME, // groupMap.put
                    entry.getKey(),
                    // 类文件在指定包名下
                    ClassName.get(packageNameForAPT, entry.getValue()));
        }

        // 遍历之后：return groupMap;
        methodBuilder.addStatement("return $N", Constants.GROUP_PARAMETER_NAME);

        // 最终生成的类文件名
        String finalClassName = Constants.GROUP_FILE_NAME + moduleName;
        messager.printMessage(Diagnostic.Kind.NOTE, "APT生成路由组Group类文件：" +
                packageNameForAPT + "." + finalClassName);

        // 生成类文件：ARouter$$Group$$app
        JavaFile.builder(packageNameForAPT, // 包名
                TypeSpec.classBuilder(finalClassName) // 类名
                        .addSuperinterface(ClassName.get(groupLoadType)) // 实现ARouterLoadGroup接口
                        .addModifiers(Modifier.PUBLIC) // public修饰符
                        .addMethod(methodBuilder.build()) // 方法的构建（方法参数 + 方法体）
                        .build()) // 类构建完成
                .build() // JavaFile构建完成
                .writeTo(filer); // 文件生成器开始生成类文件
    }

    /**
     * 赋值临时map存储，用来存放路由组Group对应的详细Path类对象，生成路由路径类文件时遍历
     *
     * @param bean 路由详细信息，最终实体封装类
     */
    private void valueOfPathMap(RouterBean bean) {
        if (checkRouterPath(bean)) {
            List<RouterBean> routerBeans = tempPathMap.get(bean.getGroup());
            if (TextUtils.isEmpty(routerBeans)) {
                routerBeans = new ArrayList<>();
                routerBeans.add(bean);
                tempPathMap.put(bean.getGroup(), routerBeans);
            } else {
//                for (RouterBean routerBean : routerBeans) {
//                    if (!bean.getPath().equalsIgnoreCase(routerBean.getPath())) {
//                        routerBeans.add(bean);
//                    }
//                }
                routerBeans.add(bean);
            }
        } else {
            messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按规范，如：/app/MainActivity");
        }
    }

    /**
     * 检验@ARouter注解的值，如果Group未填写就从必须项path中截取数据
     *
     * @param bean 路由详细信息，最终实体封装类
     * @return
     */
    private boolean checkRouterPath(RouterBean bean) {
        String group = bean.getGroup();
        String path = bean.getPath();
        //@ARouter注解的path值，必须要以 / 开头（模仿阿里ARouter框架）
        if (TextUtils.isEmpty(path) || !path.startsWith("/")) {
            messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按规范，如：/app/MainActivity");
            return false;
        }
        if (path.lastIndexOf("/") == 0) {
            messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按规范，如：/app/MainActivity");
            return false;
        }
        //从第一个 / 到第二个 / 中间截取组名
        String finalGroup = path.substring(1, path.indexOf("/", 1));
        if (finalGroup.contains("/")) {
            messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按规范，如：/app/MainActivity");
            return false;
        }
        //@ARouter注解中有Group的值
        if (!TextUtils.isEmpty(group) && !group.equals(moduleName)) {
            messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解中Group值必须和当前子模块名相同");
            return false;
        } else {
            bean.setGroup(finalGroup);
        }
        return true;
    }
}
